﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Layers;
using System.Threading;

namespace HaNoi
{
    public partial class HaNoi : UserControl
    {
        // Graphic buffer
        InteractiveBuffer buffers;
        Rectangle rect;
        SolidBrush blueBrush;
        SolidBrush redBrush;

        /* Algorithm value */
        static int towerHeight = 8;
        static Towers towers;
        int thick; // the value between to disk
        static ulong times;

        /* Threading */
        Thread thread;
        int sleepTime;
        
        bool step = false;
        bool stop = false;
        System.Windows.Forms.Timer refreshTimer;

        public HaNoi()
        {
            InitializeComponent();

            // Init refresh timer

            refreshTimer = new System.Windows.Forms.Timer();
            refreshTimer.Interval = 600;
            refreshTimer.Tick += new EventHandler(refreshTimer_Tick);
        }

        protected override void OnLoad(EventArgs e)
        {
            // Init algorithm value
            if (towers == null)
            {
                towers = new Towers(HaNoi.towerHeight);
                HaNoi.times = 0;
            }

            // Control style
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;

            this.blueBrush = new SolidBrush(Color.Blue);
            this.redBrush = new SolidBrush(Color.Red);

            this.rect = new Rectangle(0, 0, this.Width, this.Height);
            this.buffers = new InteractiveBuffer(this, rect);
            this.buffers.AddLayer("background");
            this.buffers.AddLayer("top");
            this.buffers["background"].Func += new RepresetBuffer.DrawBufferHandle(DrawBackGround);
            this.buffers["top"].Func += new RepresetBuffer.DrawBufferHandle(DrawDisk);

            this.sleepTime = 1000; // Default sleep time

        }

        protected override void OnVisibleChanged(EventArgs e)
        {

        }

        #region Drawing Region

        private void DrawBackGround(Graphics g, Rectangle r)
        {
            // column height with extra 20 pixel
            int columnHeight = this.thick * 2 * HaNoi.towerHeight + 20;

            g.Clear(Color.Moccasin);

            /* Draw first column */

            int blocks = HaNoi.towerHeight + 1;
            int x = blocks * thick;
            int y = this.Height - columnHeight;
            int width = this.thick;
            int height = columnHeight;
            Rectangle tmpRect = new Rectangle(x, y, width, height);
            g.FillRectangle(this.blueBrush, tmpRect);

            /* Draw second column */

            blocks = blocks + blocks * 2;
            x = blocks * thick;
            tmpRect = new Rectangle(x, y, width, height);
            g.FillRectangle(this.blueBrush, tmpRect);

            /* Draw third column */

            blocks = blocks + (1 + HaNoi.towerHeight) * 2;
            x = blocks * thick;
            tmpRect = new Rectangle(x, y, width, height);
            g.FillRectangle(this.blueBrush, tmpRect);

        }

        private void DrawDisk(Graphics g, Rectangle r)
        {
            int columnHeight = this.thick * 2 * HaNoi.towerHeight + 20;
            int bottom = this.Height - 2 + thick;
            // Draw tower A
            List<int> dataA = towers.GetDiskA();
            int i = 1;
            int blocks = HaNoi.towerHeight + 1;

            foreach (var item in dataA)
            {
                int x = (blocks - item) * this.thick;
                int y = bottom - (this.thick * 2 * i);
                int width = (item * 2) * this.thick + this.thick;
                Rectangle tmpRect = new Rectangle(x, y, width, thick);
                g.FillRectangle(this.redBrush, tmpRect);
                i++;
            }
            // Draw tower C (mid tower)
            List<int> dataC = towers.GetDiskC();
            i = 1;

            foreach (var item in dataC)
            {
                int x = (blocks * 3 - item) * this.thick;
                int y = bottom - (this.thick * 2 * i);
                int width = (item * 2) * this.thick + this.thick;
                Rectangle tmpRect = new Rectangle(x, y, width, thick);
                g.FillRectangle(this.redBrush, tmpRect);
                i++;
            }

            // Draw tower B
            List<int> dataB = towers.GetDiskB();
            i = 1;
            foreach (var item in dataB)
            {
                int x = (blocks * 5 - item) * this.thick;
                int y = bottom - (this.thick * 2 * i);
                int width = (item * 2) * this.thick + this.thick;
                Rectangle tmpRect = new Rectangle(x, y, width, thick);
                g.FillRectangle(this.redBrush, tmpRect);
                i++;
            }

        }

        void refreshTimer_Tick(object sender, EventArgs e)
        {
            this.Refresh();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (this.IsCompleted)
            {
                this.buffers.Render(e.Graphics);
                return;
            }
            try
            {
                this.buffers.Render(e.Graphics);
            }
            catch (Exception exc)
            {
                
            }
        }

        protected override void OnResize(EventArgs e)
        {
            int block = 7 + 6 * towerHeight; // 7 mean seperate blocks

            this.thick = this.Width / block;
            if (thick == 0)
                thick = 1;

            // Recreate buffer
            if (this.buffers != null)
            {
                this.rect = new Rectangle(0, 0, this.Width, this.Height);
                this.buffers = new InteractiveBuffer(this, rect);
                this.buffers.AddLayer("background");
                this.buffers.AddLayer("top");
                this.buffers["background"].Func += new RepresetBuffer.DrawBufferHandle(DrawBackGround);
                this.buffers["top"].Func += new RepresetBuffer.DrawBufferHandle(DrawDisk);
                this.buffers.RefreshContext();
                this.Refresh();
            }
            

        }
        #endregion

        public void ChangeTowerHeight(int newHeight)
        {
            HaNoi.towerHeight = newHeight;
            if (!ResetSimulate())
            {
                this.InitValue();
                OnResize(null);
                this.Refresh();
            }

        }

        public void ForceRefresh()
        {
            refreshTimer.Start();
        }

        #region Algorithm

        public void StartSimulate()
        {
            if (thread == null)
            {
                thread = new Thread(new ThreadStart(this.Simulate));
                HaNoi.times = 0;
                this.refreshTimer.Start();
                thread.Start();
            }
            else
            {
                this.stop = false;
            }
        }

        public void StopSimulate()
        {
            this.stop = true;
        }

        public void AbortSimulate()
        {
            if (thread != null)
            {
                thread.Abort();
            }
        }

        public bool ResetSimulate()
        {
            if (thread != null)
            {
                thread.Abort();
                InitValue();
                this.Refresh();
                this.refreshTimer.Stop();
                thread = null;
                return true;
            }
            return false;
        }

        public void Simulate()
        {
            TransferDisk(HaNoi.towerHeight, 'A', 'B', 'C');
        }

        public void Step()
        {
            this.step = true;
        }

        private void InitValue()
        {
            towers = new Towers(HaNoi.towerHeight);
            HaNoi.times = 0;
        }

        /// <summary>
        /// Main execute function
        /// </summary>
        /// <param name="diskN"></param>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="mid"></param>

        private void TransferDisk(int diskN, char source, char destination, char mid)
        {
            if (diskN > 0)
            {
                TransferDisk(diskN - 1, source, mid, destination);
                while (stop == true)
                {
                    Thread.Sleep(100);
                }
                // User request steping in simulation
                if (this.step == true)
                {
                    this.step = false;
                    while (this.step == false)
                    {
                        // Stop thread
                        Thread.Sleep(100);
                    }
                }
                else
                {
                    // Sleep for normal simulation speed
                    if (this.sleepTime != 0)
                        Thread.Sleep(this.sleepTime);
                }
                HaNoi.towers.Swap(source, destination);
                HaNoi.times++;
                TransferDisk(diskN - 1, mid, destination, source);
            }

        }

        #endregion

        public bool IsCompleted
        {
            get
            {
                return (HaNoi.towers.GetDiskB().Count == HaNoi.towerHeight && HaNoi.towers.GetDiskB().Last() == 1);
            }
        }

        /// <summary>
        /// Set delay for simulate process
        /// </summary>
        public int SleepTime
        {
            set
            {
                this.sleepTime = value;
            }
        }

        public ulong Times
        {
            get
            {
                return HaNoi.times;
            }
        }
    }

    class Towers
    {
        Dictionary<char, Disks> dic;
        Disks A;
        Disks B;
        Disks C;


        public Towers(int initvalue) 
        {
            A = new Disks(initvalue); // source
            B = new Disks(0); // Destination
            C = new Disks(0); // mid

            dic = new Dictionary<char,Disks>();
            dic.Add('A', A);
            dic.Add('B', B);
            dic.Add('C', C);
        }

        public void Swap(char src, char dest)
        {
            Disks source = dic[src];
            Disks destination = dic[dest];

            int value = source.GetTop();
            destination.AddTop(value);
        }

        /// <summary>
        /// Get disk A (source) values
        /// </summary>
        /// <returns>Integer list</returns>
        public List<int> GetDiskA() 
        {
            return this.A.GetValues;
        }

        /// <summary>
        /// Get disk B (destination) values
        /// </summary>
        /// <returns>Integer list</returns>
        public List<int> GetDiskB() 
        {
            return this.B.GetValues;
        }

        /// <summary>
        /// Get disk C (mid column) values
        /// </summary>
        /// <returns>Integer list</returns>
        public List<int> GetDiskC() 
        {
            return this.C.GetValues;
        }

    }

    class Disks
    {
        List<int> d;
        public Disks(int initvalue) 
        {
            d = new List<int>();
            for (int i = initvalue; i > 0; i--)
			{
			    d.Add(i);
			}
        }

        /// <summary>
        /// Get top disk and remove that disk
        /// </summary>
        /// <returns>Value of top disk</returns>
        public int GetTop()
        {
            int top = d.Last();
            d.RemoveAt(d.Count - 1); // remove top
            return top;
        }

        public void AddTop(int value)
        {
            d.Add(value);
        }

        /// <summary>
        /// Get value store in disks
        /// </summary>
        public List<int> GetValues
        {
            get
            {
                return this.d;
            }
        }
    }
}
